Android6.0以后引入了动态权限机制,一些系统权限的分配需要在app运行中进行分配,而不只是在AndroidManifest中指定。
本篇将针对动态权限的底层分配过程进行分析(基于Android-6.0.1)。
权限分配
我们先看一下请求分配权限的代码
1 | //frameworks/support/v4/java/android/support/v4/app/ActivityCompat.java |
requestPermissions对于Android M的前后版本都分别做了处理,Android M以上通过ActivityCompatApi23.requestPermissions进行权限的请求,而Android M以下通过PackageManager来检查Permission的分配情况。
1 | //frameworks/support/v4/api23/android/support/v4/app/ActivityCompat23.java |
ActivityCompat23将请求权限的任务交给Activity来完成,在Activity中,通过请求的permission来构造一个Intent随后启动Activity来弹出请求的界面。Intent的构造是通过PackageManager的buildRequestPermissionsIntent方法构造的。
1 | public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { |
Intent的action是ACTION_REQUEST_PERMISSIONS,它是这么定义的
1 | public static final String ACTION_REQUEST_PERMISSIONS = |
随后一个参数就是具体请求的permission数组和一个权限分派控制的相关的包名。所以activity的请求窗口是通过隐式启动的。
1 | /packages/apps/PackageInstaller/AndroidManifest.xml |
从intent-fliter可以看到,这个GrantPermissionsActivity就是我们进行权限分配的弹出窗口。GrantPermissionsActivity它的布局文件定义在packages/apps/PackageInstaller/res/layout/grant_permissions.xml,从GrantPermissionsActivity的实现来看它就是一个长的像Dialog的activity,这里我们重点关注在该Activity中对权限的允许和拒绝的处理。
1 | //packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java |
这里是通过GrantPermissionsDefaultViewHandler来控制GrantPermissionsActivity的ui视图,按钮的点击事件是通过GrantPermissionsViewHandler.ResultListener接口来处理的,GrantPermissionsActivity实现了该接口。
1 |
|
onPermissionGrantResult的三个参数分别是name代表了权限组的名字,granted表示是否进行权限分配,doNotAskAgain代表是否询问权限。内部的mRequestGrantPermissionGroups是一个LinkedHashMap<String, GroupState>,它的key是权限组名,值为GroupState,它代表了待授权的权限组Map。需要注意的是权限和权限组的概念是不同的,一个权限所属一个权限组,要给权限组可以对应多个权限。而我们传递给GrantPermissionsActivity的是权限数组(注意并不是权限组),在GrantPermissionsActivity创建的时候,会将我们请求的权限分别匹配到其对应的权限组中,这会重新计算权限组的状态。这个方法对name对应的权限组进行授权或者拒绝,然后处理下一个权限组。
1 | //packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java |
在GrantPermissionsActivity的onCreate方法中,根据请求的权限计算所属权限组的状态,首先创建AppPermissions对象,这时会去加载应用的权限组。同时遍历用于请求的权限数组并找到其对应的权限组,同时判断该权限组是否已经分配了动态权限,如果未授权则添加到待授权的权限组Map中。到这里我们还未看到真正的授权过程,在前面onPermissionGrantResult方法中,授权是通过GroupState中的成员mGroup的grantRuntimePermissions方法进一步进行权限分配的。而GroupState的定义如下
1 | private static final class GroupState { |
GroupState有三个状态STATE_UNKNOWN,STATE_ALLOWED,STATE_DENIED,它内部的mGroup实际上是个AppPermissionGroup,这些AppPermissionGroup是在AppPermissions加载的。
1 | //packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java |
权限的分配最终是通过PMS的grantRuntimePermission方法来完成的。
1 | //frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java |
在PMS的grantRuntimePermission方法中首先根据包名取到应用安装时的Package对象,这个Package对象中包含了应用的一些设置信息,通过这个设置信息可以取到当前应用的PermissionState,它维护了当前应用的权限授予情况。同时根据参数name,也就是权限名获取全新的配置信息BasePermission对象,它时从mSettings中取到的,mSettings是PMS的全局设置,它在PMS启动的时候初始化,里面包含了平台支持的所有权限。最后权限的分配进一步通过PermissionState来完成
1 | //frameworks/base/services/core/java/com/android/server/pm/PermissionsState.java |
在grantPermission方法中首先会计算当前用户进程当前拥有的组id,然后再通过ensurePermissionData将权限添加到应用的PermissionData列表中,这里返回一个PermissionData,通过该对象的grant方法进行最终的分配,事实上它其实是修改内部PermissionState成员的mGranted状态为true。最后会对用户的组id进行重新计算,如果发生变化则返回PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED,否则返回PERMISSION_OPERATION_SUCCESS
1 | //保证权限被添加到用户列表中 |
ensurePermissionData方法确保将权限对应的PermissionData添加到PermissonsState的权限列表中,后续通过computeGids计算用户userId对应的组id,并将其添加到用户的组id数组mGlobalGids中。其中内置权限的gid映射是定义在/etc/permission/platform.xml
1 | <permissions> |
至此,我们明白了权限的本质实际上就是一组gid,这组gid对应的是一些整型,这些映射关系存放在system/core/include/private/android_filesystem_config.h中,其中的定义如下
1 |
|
通过将权限映射成一组gid,然后作为补充gid赋值给用户进程,也就是权限分配的本质。
1 | //PermisssionsState.PermissionData |
通过PermissionData的grant方法,为对应的用户创建PermissionState,并将mGranted置为true表示分配了该权限给
该用户。
当然权限分配完成后,下次不需要再次分配,当我们重新启动手机后,并需要再次对权限进行分配,这是因为PMS为所有的package记录了权限分配的情况,在Android6.0之前,package所有的权限信息都是存放在data/system/packages.xml配置文件中,在应用中启动时候读取该配置就可以直到权限分配了哪些权限。但在Android6.0后,运行时权限放在了data/system/users/0/runtime-permissions.xml中,而普通权限保持不变依然存放在packages.xml中,而且默认granted就是true。那么在分配完成权限后需要将权限的分配信息持久化到该文件中。
1 | //packages.xml |
1 | <pkg name="com.feelschaotic.demo"> |
在PMS的grantRuntimePermission分配完运行时权限后,最后会调用writeRuntimePermissionsForUserLPr将权限信息持久化到配置文件runtime-permissions.xml中,我们看看这个过程
1 | public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { |
无论时同步方式还是异步方式的持久化,最后都会调用下面的方法进行
1 | //写入权限到配置文件 |
writePermissionsSync写配置的过程很简单,先打开配置文件/data/system/users/0/runtime-permissions.xml,随后对PMS中的每个package和sharedUser分别将其对应的权限分配列表按照包名和shareUserName存放在permissionsForPackage和permissionsForSharedUser中,随后打开输出流分别将其对应的运行时权限分配情况写入文件。
1 | private void writePermissions(XmlSerializer serializer, |
writePermissions负责写tag 为package下的一条权限分配信息,如
1 | <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" /> |
权限的检测
权限检测是通过Context的checkSelfPermission方法来进行的。我们看下它的实现
1 |
|
最终还是通过AMS的checkPermission来进行权限检查。
1 | //frameworks/base/core/java/android/app/ActivityManager.java |
在AMS中的一系列调用中,最终的权限还是通过PMS的checkUidPermission来进行check的。
1 | //PMS |
checkUidPermission首先根据userId从PMS的配置对象中取到SettingBase,然后取到用户对应的PermissionsState,再通过permissionsState的hasPermission判断是否有该权限。
1 | //检测权限 |
从PermissionsState的权限列表中取到PermissionData,通过PermissionData的PermissionState对象的mGranted成员就知道权限是否分配了。
总结
在Android6.0之前的版本中,应用在安装的时候会将manifest中request的权限(即通过
在Aandroid6.0之后,google为了防止应用滥用权限对权限的授予进行了收缩,将危险的权限授予过程交给用户来决定,为了适应这样的变化,必须要将安装权限和运行时权限进行区分处理,安装权限保持原有的逻辑不变,对于动态权限的分配必然要对PackageSetting进行一个大手术,在Android6.0中PackageSetting不再继承自GrantedPermissions,而是继承自于SettingBase,它的内部也比以前复杂了一些,简单来说它内部维护了一个PermissionsState,它负责管理应用的权限,因此它内部存放着应用的授权的权限列表(实际上是一个ArrayMap<String, PermissionData>),以及权限组对应的gids,此时的权限不再是仅仅是一个String,而是一个PermissionData,而PermissionData内部持有PermissionState即permission的状态,可以看到最终我们还是通过改变PermissionData的PermissionState来达到动态授权的目的。另外授予的动态权限最终会保存在runtime-permission.xml中。